/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.support.v4.app;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;

import android.content.Context;
import android.content.res.Configuration;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.v4.util.DebugUtils;
import android.support.v4.util.LogWriter;
import android.util.Log;
import android.util.SparseArray;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.AnimationSet;
import android.view.animation.AnimationUtils;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.ScaleAnimation;

/**
 * Static library support version of the framework's
 * {@link android.app.FragmentManager}. Used to write apps that run on platforms
 * prior to Android 3.0. When running on Android 3.0 or above, this
 * implementation is still used; it does not try to switch to the framework's
 * implementation. See the framework SDK documentation for a class overview.
 * 
 * <p>
 * Your activity must derive from {@link FragmentActivity} to use this.
 */
public abstract class FragmentManager {
	/**
	 * Representation of an entry on the fragment back stack, as created with
	 * {@link FragmentTransaction#addToBackStack(String)
	 * FragmentTransaction.addToBackStack()}. Entries can later be retrieved
	 * with {@link FragmentManager#getBackStackEntryAt(int)
	 * FragmentManager.getBackStackEntry()}.
	 * 
	 * <p>
	 * Note that you should never hold on to a BackStackEntry object; the
	 * identifier as returned by {@link #getId} is the only thing that will be
	 * persisted across activity instances.
	 */
	public interface BackStackEntry {
		/**
		 * Return the unique identifier for the entry. This is the only
		 * representation of the entry that will persist across activity
		 * instances.
		 */
		public int getId();

		/**
		 * Get the name that was supplied to
		 * {@link FragmentTransaction#addToBackStack(String)
		 * FragmentTransaction.addToBackStack(String)} when creating this entry.
		 */
		public String getName();

		/**
		 * Return the full bread crumb title resource identifier for the entry,
		 * or 0 if it does not have one.
		 */
		public int getBreadCrumbTitleRes();

		/**
		 * Return the short bread crumb title resource identifier for the entry,
		 * or 0 if it does not have one.
		 */
		public int getBreadCrumbShortTitleRes();

		/**
		 * Return the full bread crumb title for the entry, or null if it does
		 * not have one.
		 */
		public CharSequence getBreadCrumbTitle();

		/**
		 * Return the short bread crumb title for the entry, or null if it does
		 * not have one.
		 */
		public CharSequence getBreadCrumbShortTitle();
	}

	/**
	 * Interface to watch for changes to the back stack.
	 */
	public interface OnBackStackChangedListener {
		/**
		 * Called whenever the contents of the back stack change.
		 */
		public void onBackStackChanged();
	}

	/**
	 * Start a series of edit operations on the Fragments associated with this
	 * FragmentManager.
	 * 
	 * <p>
	 * Note: A fragment transaction can only be created/committed prior to an
	 * activity saving its state. If you try to commit a transaction after
	 * {@link FragmentActivity#onSaveInstanceState
	 * FragmentActivity.onSaveInstanceState()} (and prior to a following
	 * {@link FragmentActivity#onStart FragmentActivity.onStart} or
	 * {@link FragmentActivity#onResume FragmentActivity.onResume()}, you will
	 * get an error. This is because the framework takes care of saving your
	 * current fragments in the state, and if changes are made after the state
	 * is saved then they will be lost.
	 * </p>
	 */
	public abstract FragmentTransaction beginTransaction();

	/** @hide -- remove once prebuilts are in. */
	@Deprecated
	public FragmentTransaction openTransaction() {
		return beginTransaction();
	}

	/**
	 * After a {@link FragmentTransaction} is committed with
	 * {@link FragmentTransaction#commit FragmentTransaction.commit()}, it is
	 * scheduled to be executed asynchronously on the process's main thread. If
	 * you want to immediately executing any such pending operations, you can
	 * call this function (only from the main thread) to do so. Note that all
	 * callbacks and other related behavior will be done from within this call,
	 * so be careful about where this is called from.
	 * 
	 * @return Returns true if there were any pending transactions to be
	 *         executed.
	 */
	public abstract boolean executePendingTransactions();

	/**
	 * Finds a fragment that was identified by the given id either when inflated
	 * from XML or as the container ID when added in a transaction. This first
	 * searches through fragments that are currently added to the manager's
	 * activity; if no such fragment is found, then all fragments currently on
	 * the back stack associated with this ID are searched.
	 * 
	 * @return The fragment if found or null otherwise.
	 */
	public abstract Fragment findFragmentById(int id);

	/**
	 * Finds a fragment that was identified by the given tag either when
	 * inflated from XML or as supplied when added in a transaction. This first
	 * searches through fragments that are currently added to the manager's
	 * activity; if no such fragment is found, then all fragments currently on
	 * the back stack are searched.
	 * 
	 * @return The fragment if found or null otherwise.
	 */
	public abstract Fragment findFragmentByTag(String tag);

	/**
	 * Flag for {@link #popBackStack(String, int)} and
	 * {@link #popBackStack(int, int)}: If set, and the name or ID of a back
	 * stack entry has been supplied, then all matching entries will be consumed
	 * until one that doesn't match is found or the bottom of the stack is
	 * reached. Otherwise, all entries up to but not including that entry will
	 * be removed.
	 */
	public static final int POP_BACK_STACK_INCLUSIVE = 1 << 0;

	/**
	 * Pop the top state off the back stack. Returns true if there was one to
	 * pop, else false. This function is asynchronous -- it enqueues the request
	 * to pop, but the action will not be performed until the application
	 * returns to its event loop.
	 */
	public abstract void popBackStack();

	/**
	 * Like {@link #popBackStack()}, but performs the operation immediately
	 * inside of the call. This is like calling
	 * {@link #executePendingTransactions()} afterwards.
	 * 
	 * @return Returns true if there was something popped, else false.
	 */
	public abstract boolean popBackStackImmediate();

	/**
	 * Pop the last fragment transition from the manager's fragment back stack.
	 * If there is nothing to pop, false is returned. This function is
	 * asynchronous -- it enqueues the request to pop, but the action will not
	 * be performed until the application returns to its event loop.
	 * 
	 * @param name
	 *            If non-null, this is the name of a previous back state to look
	 *            for; if found, all states up to that state will be popped. The
	 *            {@link #POP_BACK_STACK_INCLUSIVE} flag can be used to control
	 *            whether the named state itself is popped. If null, only the
	 *            top state is popped.
	 * @param flags
	 *            Either 0 or {@link #POP_BACK_STACK_INCLUSIVE}.
	 */
	public abstract void popBackStack(String name, int flags);

	/**
	 * Like {@link #popBackStack(String, int)}, but performs the operation
	 * immediately inside of the call. This is like calling
	 * {@link #executePendingTransactions()} afterwards.
	 * 
	 * @return Returns true if there was something popped, else false.
	 */
	public abstract boolean popBackStackImmediate(String name, int flags);

	/**
	 * Pop all back stack states up to the one with the given identifier. This
	 * function is asynchronous -- it enqueues the request to pop, but the
	 * action will not be performed until the application returns to its event
	 * loop.
	 * 
	 * @param id
	 *            Identifier of the stated to be popped. If no identifier
	 *            exists, false is returned. The identifier is the number
	 *            returned by {@link FragmentTransaction#commit()
	 *            FragmentTransaction.commit()}. The
	 *            {@link #POP_BACK_STACK_INCLUSIVE} flag can be used to control
	 *            whether the named state itself is popped.
	 * @param flags
	 *            Either 0 or {@link #POP_BACK_STACK_INCLUSIVE}.
	 */
	public abstract void popBackStack(int id, int flags);

	/**
	 * Like {@link #popBackStack(int, int)}, but performs the operation
	 * immediately inside of the call. This is like calling
	 * {@link #executePendingTransactions()} afterwards.
	 * 
	 * @return Returns true if there was something popped, else false.
	 */
	public abstract boolean popBackStackImmediate(int id, int flags);

	/**
	 * Return the number of entries currently in the back stack.
	 */
	public abstract int getBackStackEntryCount();

	/**
	 * Return the BackStackEntry at index <var>index</var> in the back stack;
	 * entries start index 0 being the bottom of the stack.
	 */
	public abstract BackStackEntry getBackStackEntryAt(int index);

	/**
	 * Add a new listener for changes to the fragment back stack.
	 */
	public abstract void addOnBackStackChangedListener(
			OnBackStackChangedListener listener);

	/**
	 * Remove a listener that was previously added with
	 * {@link #addOnBackStackChangedListener(OnBackStackChangedListener)}.
	 */
	public abstract void removeOnBackStackChangedListener(
			OnBackStackChangedListener listener);

	/**
	 * Put a reference to a fragment in a Bundle. This Bundle can be persisted
	 * as saved state, and when later restoring
	 * {@link #getFragment(Bundle, String)} will return the current instance of
	 * the same fragment.
	 * 
	 * @param bundle
	 *            The bundle in which to put the fragment reference.
	 * @param key
	 *            The name of the entry in the bundle.
	 * @param fragment
	 *            The Fragment whose reference is to be stored.
	 */
	public abstract void putFragment(Bundle bundle, String key,
			Fragment fragment);

	/**
	 * Retrieve the current Fragment instance for a reference previously placed
	 * with {@link #putFragment(Bundle, String, Fragment)}.
	 * 
	 * @param bundle
	 *            The bundle from which to retrieve the fragment reference.
	 * @param key
	 *            The name of the entry in the bundle.
	 * @return Returns the current Fragment instance that is associated with the
	 *         given reference.
	 */
	public abstract Fragment getFragment(Bundle bundle, String key);

	/**
	 * Save the current instance state of the given Fragment. This can be used
	 * later when creating a new instance of the Fragment and adding it to the
	 * fragment manager, to have it create itself to match the current state
	 * returned here. Note that there are limits on how this can be used:
	 * 
	 * <ul>
	 * <li>The Fragment must currently be attached to the FragmentManager.
	 * <li>A new Fragment created using this saved state must be the same class
	 * type as the Fragment it was created from.
	 * <li>The saved state can not contain dependencies on other fragments --
	 * that is it can't use {@link #putFragment(Bundle, String, Fragment)} to
	 * store a fragment reference because that reference may not be valid when
	 * this saved state is later used. Likewise the Fragment's target and result
	 * code are not included in this state.
	 * </ul>
	 * 
	 * @param f
	 *            The Fragment whose state is to be saved.
	 * @return The generated state. This will be null if there was no
	 *         interesting state created by the fragment.
	 */
	public abstract Fragment.SavedState saveFragmentInstanceState(Fragment f);

	/**
	 * Print the FragmentManager's state into the given stream.
	 * 
	 * @param prefix
	 *            Text to print at the front of each line.
	 * @param fd
	 *            The raw file descriptor that the dump is being sent to.
	 * @param writer
	 *            A PrintWriter to which the dump is to be set.
	 * @param args
	 *            Additional arguments to the dump request.
	 */
	public abstract void dump(String prefix, FileDescriptor fd,
			PrintWriter writer, String[] args);

	/**
	 * Control whether the framework's internal fragment manager debugging logs
	 * are turned on. If enabled, you will see output in logcat as the framework
	 * performs fragment operations.
	 */
	public static void enableDebugLogging(boolean enabled) {
		FragmentManagerImpl.DEBUG = enabled;
	}
}

final class FragmentManagerState implements Parcelable {
	FragmentState[] mActive;
	int[] mAdded;
	BackStackState[] mBackStack;

	public FragmentManagerState() {
	}

	public FragmentManagerState(Parcel in) {
		mActive = in.createTypedArray(FragmentState.CREATOR);
		mAdded = in.createIntArray();
		mBackStack = in.createTypedArray(BackStackState.CREATOR);
	}

	public int describeContents() {
		return 0;
	}

	public void writeToParcel(Parcel dest, int flags) {
		dest.writeTypedArray(mActive, flags);
		dest.writeIntArray(mAdded);
		dest.writeTypedArray(mBackStack, flags);
	}

	public static final Parcelable.Creator<FragmentManagerState> CREATOR = new Parcelable.Creator<FragmentManagerState>() {
		public FragmentManagerState createFromParcel(Parcel in) {
			return new FragmentManagerState(in);
		}

		public FragmentManagerState[] newArray(int size) {
			return new FragmentManagerState[size];
		}
	};
}

/**
 * Container for fragments associated with an activity.
 */
final class FragmentManagerImpl extends FragmentManager {
	static boolean DEBUG = false;
	static final String TAG = "FragmentManager";

	static final boolean HONEYCOMB = android.os.Build.VERSION.SDK_INT >= 11;

	static final String TARGET_REQUEST_CODE_STATE_TAG = "android:target_req_state";
	static final String TARGET_STATE_TAG = "android:target_state";
	static final String VIEW_STATE_TAG = "android:view_state";

	ArrayList<Runnable> mPendingActions;
	Runnable[] mTmpActions;
	boolean mExecutingActions;

	ArrayList<Fragment> mActive;
	ArrayList<Fragment> mAdded;
	ArrayList<Integer> mAvailIndices;
	ArrayList<BackStackRecord> mBackStack;
	ArrayList<Fragment> mCreatedMenus;

	// Must be accessed while locked.
	ArrayList<BackStackRecord> mBackStackIndices;
	ArrayList<Integer> mAvailBackStackIndices;

	ArrayList<OnBackStackChangedListener> mBackStackChangeListeners;

	int mCurState = Fragment.INITIALIZING;
	SupportActivity mActivity;

	boolean mNeedMenuInvalidate;
	boolean mStateSaved;
	boolean mDestroyed;
	String mNoTransactionsBecause;

	// Temporary vars for state save and restore.
	Bundle mStateBundle = null;
	SparseArray<Parcelable> mStateArray = null;

	Runnable mExecCommit = new Runnable() {
		@Override
		public void run() {
			execPendingActions();
		}
	};

	@Override
	public FragmentTransaction beginTransaction() {
		return new BackStackRecord(this);
	}

	@Override
	public boolean executePendingTransactions() {
		return execPendingActions();
	}

	@Override
	public void popBackStack() {
		enqueueAction(new Runnable() {
			@Override
			public void run() {
				popBackStackState(
						mActivity.getInternalCallbacks().getHandler(), null,
						-1, 0);
			}
		}, false);
	}

	@Override
	public boolean popBackStackImmediate() {
		checkStateLoss();
		executePendingTransactions();
		return popBackStackState(mActivity.getInternalCallbacks().getHandler(),
				null, -1, 0);
	}

	@Override
	public void popBackStack(final String name, final int flags) {
		enqueueAction(new Runnable() {
			@Override
			public void run() {
				popBackStackState(
						mActivity.getInternalCallbacks().getHandler(), name,
						-1, flags);
			}
		}, false);
	}

	@Override
	public boolean popBackStackImmediate(String name, int flags) {
		checkStateLoss();
		executePendingTransactions();
		return popBackStackState(mActivity.getInternalCallbacks().getHandler(),
				name, -1, flags);
	}

	@Override
	public void popBackStack(final int id, final int flags) {
		if (id < 0) {
			throw new IllegalArgumentException("Bad id: " + id);
		}
		enqueueAction(new Runnable() {
			@Override
			public void run() {
				popBackStackState(
						mActivity.getInternalCallbacks().getHandler(), null,
						id, flags);
			}
		}, false);
	}

	@Override
	public boolean popBackStackImmediate(int id, int flags) {
		checkStateLoss();
		executePendingTransactions();
		if (id < 0) {
			throw new IllegalArgumentException("Bad id: " + id);
		}
		return popBackStackState(mActivity.getInternalCallbacks().getHandler(),
				null, id, flags);
	}

	@Override
	public int getBackStackEntryCount() {
		return mBackStack != null ? mBackStack.size() : 0;
	}

	@Override
	public BackStackEntry getBackStackEntryAt(int index) {
		return mBackStack.get(index);
	}

	@Override
	public void addOnBackStackChangedListener(
			OnBackStackChangedListener listener) {
		if (mBackStackChangeListeners == null) {
			mBackStackChangeListeners = new ArrayList<OnBackStackChangedListener>();
		}
		mBackStackChangeListeners.add(listener);
	}

	@Override
	public void removeOnBackStackChangedListener(
			OnBackStackChangedListener listener) {
		if (mBackStackChangeListeners != null) {
			mBackStackChangeListeners.remove(listener);
		}
	}

	@Override
	public void putFragment(Bundle bundle, String key, Fragment fragment) {
		if (fragment.mIndex < 0) {
			throw new IllegalStateException("Fragment " + fragment
					+ " is not currently in the FragmentManager");
		}
		bundle.putInt(key, fragment.mIndex);
	}

	@Override
	public Fragment getFragment(Bundle bundle, String key) {
		int index = bundle.getInt(key, -1);
		if (index == -1) {
			return null;
		}
		if (index >= mActive.size()) {
			throw new IllegalStateException(
					"Fragement no longer exists for key " + key + ": index "
							+ index);
		}
		Fragment f = mActive.get(index);
		if (f == null) {
			throw new IllegalStateException(
					"Fragement no longer exists for key " + key + ": index "
							+ index);
		}
		return f;
	}

	@Override
	public Fragment.SavedState saveFragmentInstanceState(Fragment fragment) {
		if (fragment.mIndex < 0) {
			throw new IllegalStateException("Fragment " + fragment
					+ " is not currently in the FragmentManager");
		}
		if (fragment.mState > Fragment.INITIALIZING) {
			Bundle result = saveFragmentBasicState(fragment);
			return result != null ? new Fragment.SavedState(result) : null;
		}
		return null;
	}

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder(128);
		sb.append("FragmentManager{");
		sb.append(Integer.toHexString(System.identityHashCode(this)));
		sb.append(" in ");
		DebugUtils.buildShortClassTag(mActivity, sb);
		sb.append("}}");
		return sb.toString();
	}

	@Override
	public void dump(String prefix, FileDescriptor fd, PrintWriter writer,
			String[] args) {
		String innerPrefix = prefix + "    ";

		int N;
		if (mActive != null) {
			N = mActive.size();
			if (N > 0) {
				writer.print(prefix);
				writer.print("Active Fragments in ");
				writer.print(Integer.toHexString(System.identityHashCode(this)));
				writer.println(":");
				for (int i = 0; i < N; i++) {
					Fragment f = mActive.get(i);
					writer.print(prefix);
					writer.print("  #");
					writer.print(i);
					writer.print(": ");
					writer.println(f);
					if (f != null) {
						f.dump(innerPrefix, fd, writer, args);
					}
				}
			}
		}

		if (mAdded != null) {
			N = mAdded.size();
			if (N > 0) {
				writer.print(prefix);
				writer.println("Added Fragments:");
				for (int i = 0; i < N; i++) {
					Fragment f = mAdded.get(i);
					writer.print(prefix);
					writer.print("  #");
					writer.print(i);
					writer.print(": ");
					writer.println(f.toString());
				}
			}
		}

		if (mCreatedMenus != null) {
			N = mCreatedMenus.size();
			if (N > 0) {
				writer.print(prefix);
				writer.println("Fragments Created Menus:");
				for (int i = 0; i < N; i++) {
					Fragment f = mCreatedMenus.get(i);
					writer.print(prefix);
					writer.print("  #");
					writer.print(i);
					writer.print(": ");
					writer.println(f.toString());
				}
			}
		}

		if (mBackStack != null) {
			N = mBackStack.size();
			if (N > 0) {
				writer.print(prefix);
				writer.println("Back Stack:");
				for (int i = 0; i < N; i++) {
					BackStackRecord bs = mBackStack.get(i);
					writer.print(prefix);
					writer.print("  #");
					writer.print(i);
					writer.print(": ");
					writer.println(bs.toString());
					bs.dump(innerPrefix, fd, writer, args);
				}
			}
		}

		synchronized (this) {
			if (mBackStackIndices != null) {
				N = mBackStackIndices.size();
				if (N > 0) {
					writer.print(prefix);
					writer.println("Back Stack Indices:");
					for (int i = 0; i < N; i++) {
						BackStackRecord bs = mBackStackIndices.get(i);
						writer.print(prefix);
						writer.print("  #");
						writer.print(i);
						writer.print(": ");
						writer.println(bs);
					}
				}
			}

			if (mAvailBackStackIndices != null
					&& mAvailBackStackIndices.size() > 0) {
				writer.print(prefix);
				writer.print("mAvailBackStackIndices: ");
				writer.println(Arrays.toString(mAvailBackStackIndices.toArray()));
			}
		}

		if (mPendingActions != null) {
			N = mPendingActions.size();
			if (N > 0) {
				writer.print(prefix);
				writer.println("Pending Actions:");
				for (int i = 0; i < N; i++) {
					Runnable r = mPendingActions.get(i);
					writer.print(prefix);
					writer.print("  #");
					writer.print(i);
					writer.print(": ");
					writer.println(r);
				}
			}
		}

		writer.print(prefix);
		writer.println("FragmentManager misc state:");
		writer.print(prefix);
		writer.print("  mCurState=");
		writer.print(mCurState);
		writer.print(" mStateSaved=");
		writer.print(mStateSaved);
		writer.print(" mDestroyed=");
		writer.println(mDestroyed);
		if (mNeedMenuInvalidate) {
			writer.print(prefix);
			writer.print("  mNeedMenuInvalidate=");
			writer.println(mNeedMenuInvalidate);
		}
		if (mNoTransactionsBecause != null) {
			writer.print(prefix);
			writer.print("  mNoTransactionsBecause=");
			writer.println(mNoTransactionsBecause);
		}
		if (mAvailIndices != null && mAvailIndices.size() > 0) {
			writer.print(prefix);
			writer.print("  mAvailIndices: ");
			writer.println(Arrays.toString(mAvailIndices.toArray()));
		}
	}

	static final Interpolator DECELERATE_QUINT = new DecelerateInterpolator(
			2.5f);
	static final Interpolator DECELERATE_CUBIC = new DecelerateInterpolator(
			1.5f);
	static final Interpolator ACCELERATE_QUINT = new AccelerateInterpolator(
			2.5f);
	static final Interpolator ACCELERATE_CUBIC = new AccelerateInterpolator(
			1.5f);

	static final int ANIM_DUR = 220;

	static Animation makeOpenCloseAnimation(Context context, float startScale,
			float endScale, float startAlpha, float endAlpha) {
		AnimationSet set = new AnimationSet(false);
		ScaleAnimation scale = new ScaleAnimation(startScale, endScale,
				startScale, endScale, Animation.RELATIVE_TO_SELF, .5f,
				Animation.RELATIVE_TO_SELF, .5f);
		scale.setInterpolator(DECELERATE_QUINT);
		scale.setDuration(ANIM_DUR);
		set.addAnimation(scale);
		AlphaAnimation alpha = new AlphaAnimation(startAlpha, endAlpha);
		alpha.setInterpolator(DECELERATE_CUBIC);
		alpha.setDuration(ANIM_DUR);
		set.addAnimation(alpha);
		return set;
	}

	static Animation makeFadeAnimation(Context context, float start, float end) {
		AlphaAnimation anim = new AlphaAnimation(start, end);
		anim.setInterpolator(DECELERATE_CUBIC);
		anim.setDuration(ANIM_DUR);
		return anim;
	}

	Animation loadAnimation(Fragment fragment, int transit, boolean enter,
			int transitionStyle) {
		Animation animObj = fragment.onCreateAnimation(transit, enter,
				fragment.mNextAnim);
		if (animObj != null) {
			return animObj;
		}

		if (fragment.mNextAnim != 0) {
			Animation anim = AnimationUtils.loadAnimation(
					mActivity.asActivity(), fragment.mNextAnim);
			if (anim != null) {
				return anim;
			}
		}

		if (transit == 0) {
			return null;
		}

		int styleIndex = transitToStyleIndex(transit, enter);
		if (styleIndex < 0) {
			return null;
		}

		switch (styleIndex) {
		case ANIM_STYLE_OPEN_ENTER:
			return makeOpenCloseAnimation(mActivity.asActivity(), 1.125f, 1.0f,
					0, 1);
		case ANIM_STYLE_OPEN_EXIT:
			return makeOpenCloseAnimation(mActivity.asActivity(), 1.0f, .975f,
					1, 0);
		case ANIM_STYLE_CLOSE_ENTER:
			return makeOpenCloseAnimation(mActivity.asActivity(), .975f, 1.0f,
					0, 1);
		case ANIM_STYLE_CLOSE_EXIT:
			return makeOpenCloseAnimation(mActivity.asActivity(), 1.0f, 1.075f,
					1, 0);
		case ANIM_STYLE_FADE_ENTER:
			return makeFadeAnimation(mActivity.asActivity(), 0, 1);
		case ANIM_STYLE_FADE_EXIT:
			return makeFadeAnimation(mActivity.asActivity(), 1, 0);
		}

		if (transitionStyle == 0 && mActivity.getWindow() != null) {
			transitionStyle = mActivity.getWindow().getAttributes().windowAnimations;
		}
		if (transitionStyle == 0) {
			return null;
		}

		// TypedArray attrs = mActivity.obtainStyledAttributes(transitionStyle,
		// com.android.internal.R.styleable.FragmentAnimation);
		// int anim = attrs.getResourceId(styleIndex, 0);
		// attrs.recycle();

		// if (anim == 0) {
		// return null;
		// }

		// return AnimatorInflater.loadAnimator(mActivity, anim);
		return null;
	}

	void moveToState(Fragment f, int newState, int transit, int transitionStyle) {
		// Fragments that are not currently added will sit in the onCreate()
		// state.
		if (!f.mAdded && newState > Fragment.CREATED) {
			newState = Fragment.CREATED;
		}
		if (f.mRemoving && newState > f.mState) {
			// While removing a fragment, we can't change it to a higher state.
			newState = f.mState;
		}

		if (f.mState < newState) {
			// For fragments that are created from a layout, when restoring from
			// state we don't want to allow them to be created until they are
			// being reloaded from the layout.
			if (f.mFromLayout && !f.mInLayout) {
				return;
			}
			if (f.mAnimatingAway != null) {
				// The fragment is currently being animated... but! Now we
				// want to move our state back up. Give up on waiting for the
				// animation, move to whatever the final state should be once
				// the animation is done, and then we can proceed from there.
				f.mAnimatingAway = null;
				moveToState(f, f.mStateAfterAnimating, 0, 0);
			}
			switch (f.mState) {
			case Fragment.INITIALIZING:
				if (DEBUG)
					Log.v(TAG, "moveto CREATED: " + f);
				if (f.mSavedFragmentState != null) {
					f.mSavedViewState = f.mSavedFragmentState
							.getSparseParcelableArray(FragmentManagerImpl.VIEW_STATE_TAG);
					f.mTarget = getFragment(f.mSavedFragmentState,
							FragmentManagerImpl.TARGET_STATE_TAG);
					if (f.mTarget != null) {
						f.mTargetRequestCode = f.mSavedFragmentState
								.getInt(FragmentManagerImpl.TARGET_REQUEST_CODE_STATE_TAG,
										0);
					}
				}
				f.mActivity = mActivity;
				f.mFragmentManager = mActivity.getInternalCallbacks()
						.getFragments();
				f.mCalled = false;
				f.onAttach(mActivity);
				if (!f.mCalled) {
					throw new SuperNotCalledException("Fragment " + f
							+ " did not call through to super.onAttach()");
				}
				mActivity.onAttachFragment(f);

				if (!f.mRetaining) {
					f.mCalled = false;
					f.onCreate(f.mSavedFragmentState);
					if (!f.mCalled) {
						throw new SuperNotCalledException("Fragment " + f
								+ " did not call through to super.onCreate()");
					}
				}
				f.mRetaining = false;
				if (f.mFromLayout) {
					// For fragments that are part of the content view
					// layout, we need to instantiate the view immediately
					// and the inflater will take care of adding it.
					f.mView = f.onCreateView(
							f.getLayoutInflater(f.mSavedFragmentState), null,
							f.mSavedFragmentState);
					if (f.mView != null) {
						f.mInnerView = f.mView;
						f.mView = NoSaveStateFrameLayout.wrap(f.mView);
						if (f.mHidden)
							f.mView.setVisibility(View.GONE);
						f.onViewCreated(f.mView, f.mSavedFragmentState);
					} else {
						f.mInnerView = null;
					}
				}
			case Fragment.CREATED:
				if (newState > Fragment.CREATED) {
					if (DEBUG)
						Log.v(TAG, "moveto ACTIVITY_CREATED: " + f);
					if (!f.mFromLayout) {
						ViewGroup container = null;
						if (f.mContainerId != 0) {
							container = (ViewGroup) mActivity
									.findViewById(f.mContainerId);
							if (container == null && !f.mRestored) {
								throw new IllegalArgumentException(
										"No view found for id 0x"
												+ Integer
														.toHexString(f.mContainerId)
												+ " for fragment " + f);
							}
						}
						f.mContainer = container;
						f.mView = f.onCreateView(
								f.getLayoutInflater(f.mSavedFragmentState),
								container, f.mSavedFragmentState);
						if (f.mView != null) {
							f.mInnerView = f.mView;
							f.mView = NoSaveStateFrameLayout.wrap(f.mView);
							if (container != null) {
								Animation anim = loadAnimation(f, transit,
										true, transitionStyle);
								if (anim != null) {
									f.mView.startAnimation(anim);
								}
								container.addView(f.mView);
							}
							if (f.mHidden)
								f.mView.setVisibility(View.GONE);
							f.onViewCreated(f.mView, f.mSavedFragmentState);
						} else {
							f.mInnerView = null;
						}
					}

					f.mCalled = false;
					f.onActivityCreated(f.mSavedFragmentState);
					if (!f.mCalled) {
						throw new SuperNotCalledException(
								"Fragment "
										+ f
										+ " did not call through to super.onActivityCreated()");
					}
					if (f.mView != null) {
						f.restoreViewState();
					}
					f.mSavedFragmentState = null;
				}
			case Fragment.ACTIVITY_CREATED:
			case Fragment.STOPPED:
				if (newState > Fragment.STOPPED) {
					if (DEBUG)
						Log.v(TAG, "moveto STARTED: " + f);
					f.mCalled = false;
					f.performStart();
					if (!f.mCalled) {
						throw new SuperNotCalledException("Fragment " + f
								+ " did not call through to super.onStart()");
					}
				}
			case Fragment.STARTED:
				if (newState > Fragment.STARTED) {
					if (DEBUG)
						Log.v(TAG, "moveto RESUMED: " + f);
					f.mCalled = false;
					f.mResumed = true;
					f.onResume();
					if (!f.mCalled) {
						throw new SuperNotCalledException("Fragment " + f
								+ " did not call through to super.onResume()");
					}
					f.mSavedFragmentState = null;
					f.mSavedViewState = null;
				}
			}
		} else if (f.mState > newState) {
			switch (f.mState) {
			case Fragment.RESUMED:
				if (newState < Fragment.RESUMED) {
					if (DEBUG)
						Log.v(TAG, "movefrom RESUMED: " + f);
					f.mCalled = false;
					f.onPause();
					if (!f.mCalled) {
						throw new SuperNotCalledException("Fragment " + f
								+ " did not call through to super.onPause()");
					}
					f.mResumed = false;
				}
			case Fragment.STARTED:
				if (newState < Fragment.STARTED) {
					if (DEBUG)
						Log.v(TAG, "movefrom STARTED: " + f);
					f.mCalled = false;
					f.performStop();
					if (!f.mCalled) {
						throw new SuperNotCalledException("Fragment " + f
								+ " did not call through to super.onStop()");
					}
				}
			case Fragment.STOPPED:
				if (newState < Fragment.STOPPED) {
					if (DEBUG)
						Log.v(TAG, "movefrom STOPPED: " + f);
					f.performReallyStop();
				}
			case Fragment.ACTIVITY_CREATED:
				if (newState < Fragment.ACTIVITY_CREATED) {
					if (DEBUG)
						Log.v(TAG, "movefrom ACTIVITY_CREATED: " + f);
					if (f.mView != null) {
						// Need to save the current view state if not
						// done already.
						if (!mActivity.isFinishing()
								&& f.mSavedViewState == null) {
							saveFragmentViewState(f);
						}
					}
					f.mCalled = false;
					f.performDestroyView();
					if (!f.mCalled) {
						throw new SuperNotCalledException(
								"Fragment "
										+ f
										+ " did not call through to super.onDestroyView()");
					}
					if (f.mView != null && f.mContainer != null) {
						Animation anim = null;
						if (mCurState > Fragment.INITIALIZING && !mDestroyed) {
							anim = loadAnimation(f, transit, false,
									transitionStyle);
						}
						if (anim != null) {
							final Fragment fragment = f;
							f.mAnimatingAway = f.mView;
							f.mStateAfterAnimating = newState;
							anim.setAnimationListener(new AnimationListener() {
								@Override
								public void onAnimationEnd(Animation animation) {
									if (fragment.mAnimatingAway != null) {
										fragment.mAnimatingAway = null;
										moveToState(fragment,
												fragment.mStateAfterAnimating,
												0, 0);
									}
								}

								@Override
								public void onAnimationRepeat(
										Animation animation) {
								}

								@Override
								public void onAnimationStart(Animation animation) {
								}
							});
							f.mView.startAnimation(anim);
						}
						f.mContainer.removeView(f.mView);
					}
					f.mContainer = null;
					f.mView = null;
					f.mInnerView = null;
				}
			case Fragment.CREATED:
				if (newState < Fragment.CREATED) {
					if (mDestroyed) {
						if (f.mAnimatingAway != null) {
							// The fragment's containing activity is
							// being destroyed, but this fragment is
							// currently animating away. Stop the
							// animation right now -- it is not needed,
							// and we can't wait any more on destroying
							// the fragment.
							View v = f.mAnimatingAway;
							f.mAnimatingAway = null;
							v.clearAnimation();
						}
					}
					if (f.mAnimatingAway != null) {
						// We are waiting for the fragment's view to finish
						// animating away. Just make a note of the state
						// the fragment now should move to once the animation
						// is done.
						f.mStateAfterAnimating = newState;
						newState = Fragment.CREATED;
					} else {
						if (DEBUG)
							Log.v(TAG, "movefrom CREATED: " + f);
						if (!f.mRetaining) {
							f.mCalled = false;
							f.onDestroy();
							if (!f.mCalled) {
								throw new SuperNotCalledException(
										"Fragment "
												+ f
												+ " did not call through to super.onDestroy()");
							}
						}

						f.mCalled = false;
						f.onDetach();
						if (!f.mCalled) {
							throw new SuperNotCalledException(
									"Fragment "
											+ f
											+ " did not call through to super.onDetach()");
						}
						if (!f.mRetaining) {
							makeInactive(f);
						} else {
							f.mActivity = null;
							f.mFragmentManager = null;
						}
					}
				}
			}
		}

		f.mState = newState;
	}

	void moveToState(Fragment f) {
		moveToState(f, mCurState, 0, 0);
	}

	void moveToState(int newState, boolean always) {
		moveToState(newState, 0, 0, always);
	}

	void moveToState(int newState, int transit, int transitStyle, boolean always) {
		if (mActivity == null && newState != Fragment.INITIALIZING) {
			throw new IllegalStateException("No activity");
		}

		if (!always && mCurState == newState) {
			return;
		}

		mCurState = newState;
		if (mActive != null) {
			for (int i = 0; i < mActive.size(); i++) {
				Fragment f = mActive.get(i);
				if (f != null) {
					moveToState(f, newState, transit, transitStyle);
				}
			}

			if (mNeedMenuInvalidate && mActivity != null
					&& mCurState == Fragment.RESUMED) {
				mActivity.supportInvalidateOptionsMenu();
				mNeedMenuInvalidate = false;
			}
		}
	}

	void makeActive(Fragment f) {
		if (f.mIndex >= 0) {
			return;
		}

		if (mAvailIndices == null || mAvailIndices.size() <= 0) {
			if (mActive == null) {
				mActive = new ArrayList<Fragment>();
			}
			f.setIndex(mActive.size());
			mActive.add(f);

		} else {
			f.setIndex(mAvailIndices.remove(mAvailIndices.size() - 1));
			mActive.set(f.mIndex, f);
		}
	}

	void makeInactive(Fragment f) {
		if (f.mIndex < 0) {
			return;
		}

		if (DEBUG)
			Log.v(TAG, "Freeing fragment index " + f.mIndex);
		mActive.set(f.mIndex, null);
		if (mAvailIndices == null) {
			mAvailIndices = new ArrayList<Integer>();
		}
		mAvailIndices.add(f.mIndex);
		mActivity.getInternalCallbacks().invalidateSupportFragmentIndex(
				f.mIndex);
		f.initState();
	}

	public void addFragment(Fragment fragment, boolean moveToStateNow) {
		if (mAdded == null) {
			mAdded = new ArrayList<Fragment>();
		}
		if (DEBUG)
			Log.v(TAG, "add: " + fragment);
		makeActive(fragment);
		if (!fragment.mDetached) {
			mAdded.add(fragment);
			fragment.mAdded = true;
			fragment.mRemoving = false;
			if (fragment.mHasMenu && fragment.mMenuVisible) {
				mNeedMenuInvalidate = true;
			}
			if (moveToStateNow) {
				moveToState(fragment);
			}
		}
	}

	public void removeFragment(Fragment fragment, int transition,
			int transitionStyle) {
		if (DEBUG)
			Log.v(TAG, "remove: " + fragment + " nesting="
					+ fragment.mBackStackNesting);
		final boolean inactive = !fragment.isInBackStack();
		if (!fragment.mDetached || inactive) {
			mAdded.remove(fragment);
			if (fragment.mHasMenu && fragment.mMenuVisible) {
				mNeedMenuInvalidate = true;
			}
			fragment.mAdded = false;
			fragment.mRemoving = true;
			moveToState(fragment, inactive ? Fragment.INITIALIZING
					: Fragment.CREATED, transition, transitionStyle);
		}
	}

	public void hideFragment(Fragment fragment, int transition,
			int transitionStyle) {
		if (DEBUG)
			Log.v(TAG, "hide: " + fragment);
		if (!fragment.mHidden) {
			fragment.mHidden = true;
			if (fragment.mView != null) {
				Animation anim = loadAnimation(fragment, transition, true,
						transitionStyle);
				if (anim != null) {
					fragment.mView.startAnimation(anim);
				}
				fragment.mView.setVisibility(View.GONE);
			}
			if (fragment.mAdded && fragment.mHasMenu && fragment.mMenuVisible) {
				mNeedMenuInvalidate = true;
			}
			fragment.onHiddenChanged(true);
		}
	}

	public void showFragment(Fragment fragment, int transition,
			int transitionStyle) {
		if (DEBUG)
			Log.v(TAG, "show: " + fragment);
		if (fragment.mHidden) {
			fragment.mHidden = false;
			if (fragment.mView != null) {
				Animation anim = loadAnimation(fragment, transition, true,
						transitionStyle);
				if (anim != null) {
					fragment.mView.startAnimation(anim);
				}
				fragment.mView.setVisibility(View.VISIBLE);
			}
			if (fragment.mAdded && fragment.mHasMenu && fragment.mMenuVisible) {
				mNeedMenuInvalidate = true;
			}
			fragment.onHiddenChanged(false);
		}
	}

	public void detachFragment(Fragment fragment, int transition,
			int transitionStyle) {
		if (DEBUG)
			Log.v(TAG, "detach: " + fragment);
		if (!fragment.mDetached) {
			fragment.mDetached = true;
			if (fragment.mAdded) {
				// We are not already in back stack, so need to remove the
				// fragment.
				mAdded.remove(fragment);
				if (fragment.mHasMenu && fragment.mMenuVisible) {
					mNeedMenuInvalidate = true;
				}
				fragment.mAdded = false;
				moveToState(fragment, Fragment.CREATED, transition,
						transitionStyle);
			}
		}
	}

	public void attachFragment(Fragment fragment, int transition,
			int transitionStyle) {
		if (DEBUG)
			Log.v(TAG, "attach: " + fragment);
		if (fragment.mDetached) {
			fragment.mDetached = false;
			if (!fragment.mAdded) {
				mAdded.add(fragment);
				fragment.mAdded = true;
				if (fragment.mHasMenu && fragment.mMenuVisible) {
					mNeedMenuInvalidate = true;
				}
				moveToState(fragment, mCurState, transition, transitionStyle);
			}
		}
	}

	public Fragment findFragmentById(int id) {
		if (mActive != null) {
			// First look through added fragments.
			for (int i = mAdded.size() - 1; i >= 0; i--) {
				Fragment f = mAdded.get(i);
				if (f != null && f.mFragmentId == id) {
					return f;
				}
			}
			// Now for any known fragment.
			for (int i = mActive.size() - 1; i >= 0; i--) {
				Fragment f = mActive.get(i);
				if (f != null && f.mFragmentId == id) {
					return f;
				}
			}
		}
		return null;
	}

	public Fragment findFragmentByTag(String tag) {
		if (mActive != null && tag != null) {
			// First look through added fragments.
			for (int i = mAdded.size() - 1; i >= 0; i--) {
				Fragment f = mAdded.get(i);
				if (f != null && tag.equals(f.mTag)) {
					return f;
				}
			}
			// Now for any known fragment.
			for (int i = mActive.size() - 1; i >= 0; i--) {
				Fragment f = mActive.get(i);
				if (f != null && tag.equals(f.mTag)) {
					return f;
				}
			}
		}
		return null;
	}

	public Fragment findFragmentByWho(String who) {
		if (mActive != null && who != null) {
			for (int i = mActive.size() - 1; i >= 0; i--) {
				Fragment f = mActive.get(i);
				if (f != null && who.equals(f.mWho)) {
					return f;
				}
			}
		}
		return null;
	}

	private void checkStateLoss() {
		if (mStateSaved) {
			throw new IllegalStateException(
					"Can not perform this action after onSaveInstanceState");
		}
		if (mNoTransactionsBecause != null) {
			throw new IllegalStateException(
					"Can not perform this action inside of "
							+ mNoTransactionsBecause);
		}
	}

	public void enqueueAction(Runnable action, boolean allowStateLoss) {
		if (!allowStateLoss) {
			checkStateLoss();
		}
		synchronized (this) {
			if (mActivity == null) {
				throw new IllegalStateException("Activity has been destroyed");
			}
			if (mPendingActions == null) {
				mPendingActions = new ArrayList<Runnable>();
			}
			mPendingActions.add(action);
			if (mPendingActions.size() == 1) {
				mActivity.getInternalCallbacks().getHandler()
						.removeCallbacks(mExecCommit);
				mActivity.getInternalCallbacks().getHandler().post(mExecCommit);
			}
		}
	}

	public int allocBackStackIndex(BackStackRecord bse) {
		synchronized (this) {
			if (mAvailBackStackIndices == null
					|| mAvailBackStackIndices.size() <= 0) {
				if (mBackStackIndices == null) {
					mBackStackIndices = new ArrayList<BackStackRecord>();
				}
				int index = mBackStackIndices.size();
				if (DEBUG)
					Log.v(TAG, "Setting back stack index " + index + " to "
							+ bse);
				mBackStackIndices.add(bse);
				return index;

			} else {
				int index = mAvailBackStackIndices
						.remove(mAvailBackStackIndices.size() - 1);
				if (DEBUG)
					Log.v(TAG, "Adding back stack index " + index + " with "
							+ bse);
				mBackStackIndices.set(index, bse);
				return index;
			}
		}
	}

	public void setBackStackIndex(int index, BackStackRecord bse) {
		synchronized (this) {
			if (mBackStackIndices == null) {
				mBackStackIndices = new ArrayList<BackStackRecord>();
			}
			int N = mBackStackIndices.size();
			if (index < N) {
				if (DEBUG)
					Log.v(TAG, "Setting back stack index " + index + " to "
							+ bse);
				mBackStackIndices.set(index, bse);
			} else {
				while (N < index) {
					mBackStackIndices.add(null);
					if (mAvailBackStackIndices == null) {
						mAvailBackStackIndices = new ArrayList<Integer>();
					}
					if (DEBUG)
						Log.v(TAG, "Adding available back stack index " + N);
					mAvailBackStackIndices.add(N);
					N++;
				}
				if (DEBUG)
					Log.v(TAG, "Adding back stack index " + index + " with "
							+ bse);
				mBackStackIndices.add(bse);
			}
		}
	}

	public void freeBackStackIndex(int index) {
		synchronized (this) {
			mBackStackIndices.set(index, null);
			if (mAvailBackStackIndices == null) {
				mAvailBackStackIndices = new ArrayList<Integer>();
			}
			if (DEBUG)
				Log.v(TAG, "Freeing back stack index " + index);
			mAvailBackStackIndices.add(index);
		}
	}

	/**
	 * Only call from main thread!
	 */
	public boolean execPendingActions() {
		if (mExecutingActions) {
			throw new IllegalStateException(
					"Recursive entry to executePendingTransactions");
		}

		if (Looper.myLooper() != mActivity.getInternalCallbacks().getHandler()
				.getLooper()) {
			throw new IllegalStateException(
					"Must be called from main thread of process");
		}

		boolean didSomething = false;

		while (true) {
			int numActions;

			synchronized (this) {
				if (mPendingActions == null || mPendingActions.size() == 0) {
					return didSomething;
				}

				numActions = mPendingActions.size();
				if (mTmpActions == null || mTmpActions.length < numActions) {
					mTmpActions = new Runnable[numActions];
				}
				mPendingActions.toArray(mTmpActions);
				mPendingActions.clear();
				mActivity.getInternalCallbacks().getHandler()
						.removeCallbacks(mExecCommit);
			}

			mExecutingActions = true;
			for (int i = 0; i < numActions; i++) {
				mTmpActions[i].run();
				mTmpActions[i] = null;
			}
			mExecutingActions = false;
			didSomething = true;
		}
	}

	void reportBackStackChanged() {
		if (mBackStackChangeListeners != null) {
			for (int i = 0; i < mBackStackChangeListeners.size(); i++) {
				mBackStackChangeListeners.get(i).onBackStackChanged();
			}
		}
	}

	void addBackStackState(BackStackRecord state) {
		if (mBackStack == null) {
			mBackStack = new ArrayList<BackStackRecord>();
		}
		mBackStack.add(state);
		reportBackStackChanged();
	}

	boolean popBackStackState(Handler handler, String name, int id, int flags) {
		if (mBackStack == null) {
			return false;
		}
		if (name == null && id < 0 && (flags & POP_BACK_STACK_INCLUSIVE) == 0) {
			int last = mBackStack.size() - 1;
			if (last < 0) {
				return false;
			}
			final BackStackRecord bss = mBackStack.remove(last);
			bss.popFromBackStack(true);
			reportBackStackChanged();
		} else {
			int index = -1;
			if (name != null || id >= 0) {
				// If a name or ID is specified, look for that place in
				// the stack.
				index = mBackStack.size() - 1;
				while (index >= 0) {
					BackStackRecord bss = mBackStack.get(index);
					if (name != null && name.equals(bss.getName())) {
						break;
					}
					if (id >= 0 && id == bss.mIndex) {
						break;
					}
					index--;
				}
				if (index < 0) {
					return false;
				}
				if ((flags & POP_BACK_STACK_INCLUSIVE) != 0) {
					index--;
					// Consume all following entries that match.
					while (index >= 0) {
						BackStackRecord bss = mBackStack.get(index);
						if ((name != null && name.equals(bss.getName()))
								|| (id >= 0 && id == bss.mIndex)) {
							index--;
							continue;
						}
						break;
					}
				}
			}
			if (index == mBackStack.size() - 1) {
				return false;
			}
			final ArrayList<BackStackRecord> states = new ArrayList<BackStackRecord>();
			for (int i = mBackStack.size() - 1; i > index; i--) {
				states.add(mBackStack.remove(i));
			}
			final int LAST = states.size() - 1;
			for (int i = 0; i <= LAST; i++) {
				if (DEBUG)
					Log.v(TAG, "Popping back stack state: " + states.get(i));
				states.get(i).popFromBackStack(i == LAST);
			}
			reportBackStackChanged();
		}
		return true;
	}

	ArrayList<Fragment> retainNonConfig() {
		ArrayList<Fragment> fragments = null;
		if (mActive != null) {
			for (int i = 0; i < mActive.size(); i++) {
				Fragment f = mActive.get(i);
				if (f != null && f.mRetainInstance) {
					if (fragments == null) {
						fragments = new ArrayList<Fragment>();
					}
					fragments.add(f);
					f.mRetaining = true;
					f.mTargetIndex = f.mTarget != null ? f.mTarget.mIndex : -1;
				}
			}
		}
		return fragments;
	}

	void saveFragmentViewState(Fragment f) {
		if (f.mInnerView == null) {
			return;
		}
		if (mStateArray == null) {
			mStateArray = new SparseArray<Parcelable>();
		} else {
			mStateArray.clear();
		}
		f.mInnerView.saveHierarchyState(mStateArray);
		if (mStateArray.size() > 0) {
			f.mSavedViewState = mStateArray;
			mStateArray = null;
		}
	}

	Bundle saveFragmentBasicState(Fragment f) {
		Bundle result = null;

		if (mStateBundle == null) {
			mStateBundle = new Bundle();
		}
		f.onSaveInstanceState(mStateBundle);
		if (!mStateBundle.isEmpty()) {
			result = mStateBundle;
			mStateBundle = null;
		}

		if (f.mView != null) {
			saveFragmentViewState(f);
		}
		if (f.mSavedViewState != null) {
			if (result == null) {
				result = new Bundle();
			}
			result.putSparseParcelableArray(FragmentManagerImpl.VIEW_STATE_TAG,
					f.mSavedViewState);
		}

		return result;
	}

	Parcelable saveAllState() {
		// Make sure all pending operations have now been executed to get
		// our state update-to-date.
		execPendingActions();

		if (HONEYCOMB) {
			// As of Honeycomb, we save state after pausing. Prior to that
			// it is before pausing. With fragments this is an issue, since
			// there are many things you may do after pausing but before
			// stopping that change the fragment state. For those older
			// devices, we will not at this point say that we have saved
			// the state, so we will allow them to continue doing fragment
			// transactions. This retains the same semantics as Honeycomb,
			// though you do have the risk of losing the very most recent state
			// if the process is killed... we'll live with that.
			mStateSaved = true;
		}

		if (mActive == null || mActive.size() <= 0) {
			return null;
		}

		// First collect all active fragments.
		int N = mActive.size();
		FragmentState[] active = new FragmentState[N];
		boolean haveFragments = false;
		for (int i = 0; i < N; i++) {
			Fragment f = mActive.get(i);
			if (f != null) {
				haveFragments = true;

				FragmentState fs = new FragmentState(f);
				active[i] = fs;

				if (f.mState > Fragment.INITIALIZING
						&& fs.mSavedFragmentState == null) {
					fs.mSavedFragmentState = saveFragmentBasicState(f);

					if (f.mTarget != null) {
						if (f.mTarget.mIndex < 0) {
							String msg = "Failure saving state: " + f
									+ " has target not in fragment manager: "
									+ f.mTarget;
							Log.e(TAG, msg);
							dump("  ", null,
									new PrintWriter(new LogWriter(TAG)),
									new String[] {});
							throw new IllegalStateException(msg);
						}
						if (fs.mSavedFragmentState == null) {
							fs.mSavedFragmentState = new Bundle();
						}
						putFragment(fs.mSavedFragmentState,
								FragmentManagerImpl.TARGET_STATE_TAG, f.mTarget);
						if (f.mTargetRequestCode != 0) {
							fs.mSavedFragmentState
									.putInt(FragmentManagerImpl.TARGET_REQUEST_CODE_STATE_TAG,
											f.mTargetRequestCode);
						}
					}

				} else {
					fs.mSavedFragmentState = f.mSavedFragmentState;
				}

				if (DEBUG)
					Log.v(TAG, "Saved state of " + f + ": "
							+ fs.mSavedFragmentState);
			}
		}

		if (!haveFragments) {
			if (DEBUG)
				Log.v(TAG, "saveAllState: no fragments!");
			return null;
		}

		int[] added = null;
		BackStackState[] backStack = null;

		// Build list of currently added fragments.
		if (mAdded != null) {
			N = mAdded.size();
			if (N > 0) {
				added = new int[N];
				for (int i = 0; i < N; i++) {
					added[i] = mAdded.get(i).mIndex;
					if (DEBUG)
						Log.v(TAG, "saveAllState: adding fragment #" + i + ": "
								+ mAdded.get(i));
				}
			}
		}

		// Now save back stack.
		if (mBackStack != null) {
			N = mBackStack.size();
			if (N > 0) {
				backStack = new BackStackState[N];
				for (int i = 0; i < N; i++) {
					backStack[i] = new BackStackState(this, mBackStack.get(i));
					if (DEBUG)
						Log.v(TAG, "saveAllState: adding back stack #" + i
								+ ": " + mBackStack.get(i));
				}
			}
		}

		FragmentManagerState fms = new FragmentManagerState();
		fms.mActive = active;
		fms.mAdded = added;
		fms.mBackStack = backStack;
		return fms;
	}

	void restoreAllState(Parcelable state, ArrayList<Fragment> nonConfig) {
		// If there is no saved state at all, then there can not be
		// any nonConfig fragments either, so that is that.
		if (state == null)
			return;
		FragmentManagerState fms = (FragmentManagerState) state;
		if (fms.mActive == null)
			return;

		// First re-attach any non-config instances we are retaining back
		// to their saved state, so we don't try to instantiate them again.
		if (nonConfig != null) {
			for (int i = 0; i < nonConfig.size(); i++) {
				Fragment f = nonConfig.get(i);
				if (DEBUG)
					Log.v(TAG, "restoreAllState: re-attaching retained " + f);
				FragmentState fs = fms.mActive[f.mIndex];
				fs.mInstance = f;
				f.mSavedViewState = null;
				f.mBackStackNesting = 0;
				f.mInLayout = false;
				f.mAdded = false;
				f.mTarget = null;
				if (fs.mSavedFragmentState != null) {
					fs.mSavedFragmentState.setClassLoader(mActivity
							.getClassLoader());
					f.mSavedViewState = fs.mSavedFragmentState
							.getSparseParcelableArray(FragmentManagerImpl.VIEW_STATE_TAG);
				}
			}
		}

		// Build the full list of active fragments, instantiating them from
		// their saved state.
		mActive = new ArrayList<Fragment>(fms.mActive.length);
		if (mAvailIndices != null) {
			mAvailIndices.clear();
		}
		for (int i = 0; i < fms.mActive.length; i++) {
			FragmentState fs = fms.mActive[i];
			if (fs != null) {
				Fragment f = fs.instantiate(mActivity);
				if (DEBUG)
					Log.v(TAG, "restoreAllState: adding #" + i + ": " + f);
				mActive.add(f);
				// Now that the fragment is instantiated (or came from being
				// retained above), clear mInstance in case we end up
				// re-restoring
				// from this FragmentState again.
				fs.mInstance = null;
			} else {
				if (DEBUG)
					Log.v(TAG, "restoreAllState: adding #" + i + ": (null)");
				mActive.add(null);
				if (mAvailIndices == null) {
					mAvailIndices = new ArrayList<Integer>();
				}
				if (DEBUG)
					Log.v(TAG, "restoreAllState: adding avail #" + i);
				mAvailIndices.add(i);
			}
		}

		// Update the target of all retained fragments.
		if (nonConfig != null) {
			for (int i = 0; i < nonConfig.size(); i++) {
				Fragment f = nonConfig.get(i);
				if (f.mTargetIndex >= 0) {
					if (f.mTargetIndex < mActive.size()) {
						f.mTarget = mActive.get(f.mTargetIndex);
					} else {
						Log.w(TAG, "Re-attaching retained fragment " + f
								+ " target no longer exists: " + f.mTargetIndex);
						f.mTarget = null;
					}
				}
			}
		}

		// Build the list of currently added fragments.
		if (fms.mAdded != null) {
			mAdded = new ArrayList<Fragment>(fms.mAdded.length);
			for (int i = 0; i < fms.mAdded.length; i++) {
				Fragment f = mActive.get(fms.mAdded[i]);
				if (f == null) {
					throw new IllegalStateException(
							"No instantiated fragment for index #"
									+ fms.mAdded[i]);
				}
				f.mAdded = true;
				if (DEBUG)
					Log.v(TAG, "restoreAllState: making added #" + i + ": " + f);
				mAdded.add(f);
			}
		} else {
			mAdded = null;
		}

		// Build the back stack.
		if (fms.mBackStack != null) {
			mBackStack = new ArrayList<BackStackRecord>(fms.mBackStack.length);
			for (int i = 0; i < fms.mBackStack.length; i++) {
				BackStackRecord bse = fms.mBackStack[i].instantiate(this);
				if (DEBUG)
					Log.v(TAG, "restoreAllState: adding bse #" + i + " (index "
							+ bse.mIndex + "): " + bse);
				mBackStack.add(bse);
				if (bse.mIndex >= 0) {
					setBackStackIndex(bse.mIndex, bse);
				}
			}
		} else {
			mBackStack = null;
		}
	}

	public void attachActivity(SupportActivity activity) {
		if (mActivity != null)
			throw new IllegalStateException();
		mActivity = activity;
	}

	public void noteStateNotSaved() {
		mStateSaved = false;
	}

	public void dispatchCreate() {
		mStateSaved = false;
		moveToState(Fragment.CREATED, false);
	}

	public void dispatchActivityCreated() {
		mStateSaved = false;
		moveToState(Fragment.ACTIVITY_CREATED, false);
	}

	public void dispatchStart() {
		mStateSaved = false;
		moveToState(Fragment.STARTED, false);
	}

	public void dispatchResume() {
		mStateSaved = false;
		moveToState(Fragment.RESUMED, false);
	}

	public void dispatchPause() {
		moveToState(Fragment.STARTED, false);
	}

	public void dispatchStop() {
		// See saveAllState() for the explanation of this. We do this for
		// all platform versions, to keep our behavior more consistent between
		// them.
		mStateSaved = true;

		moveToState(Fragment.STOPPED, false);
	}

	public void dispatchReallyStop() {
		moveToState(Fragment.ACTIVITY_CREATED, false);
	}

	public void dispatchDestroy() {
		mDestroyed = true;
		execPendingActions();
		moveToState(Fragment.INITIALIZING, false);
		mActivity = null;
	}

	public void dispatchConfigurationChanged(Configuration newConfig) {
		if (mActive != null) {
			for (int i = 0; i < mAdded.size(); i++) {
				Fragment f = mAdded.get(i);
				if (f != null) {
					f.onConfigurationChanged(newConfig);
				}
			}
		}
	}

	public void dispatchLowMemory() {
		if (mActive != null) {
			for (int i = 0; i < mAdded.size(); i++) {
				Fragment f = mAdded.get(i);
				if (f != null) {
					f.onLowMemory();
				}
			}
		}
	}

	public boolean dispatchCreateOptionsMenu(Menu menu, MenuInflater inflater) {
		boolean show = false;
		ArrayList<Fragment> newMenus = null;
		if (mActive != null) {
			for (int i = 0; i < mAdded.size(); i++) {
				Fragment f = mAdded.get(i);
				if (f != null && !f.mHidden && f.mHasMenu && f.mMenuVisible) {
					show = true;
					f.onCreateOptionsMenu(menu, inflater);
					if (newMenus == null) {
						newMenus = new ArrayList<Fragment>();
					}
					newMenus.add(f);
				}
			}
		}

		if (mCreatedMenus != null) {
			for (int i = 0; i < mCreatedMenus.size(); i++) {
				Fragment f = mCreatedMenus.get(i);
				if (newMenus == null || !newMenus.contains(f)) {
					f.onDestroyOptionsMenu();
				}
			}
		}

		mCreatedMenus = newMenus;

		return show;
	}

	public boolean dispatchPrepareOptionsMenu(Menu menu) {
		boolean show = false;
		if (mActive != null) {
			for (int i = 0; i < mAdded.size(); i++) {
				Fragment f = mAdded.get(i);
				if (f != null && !f.mHidden && f.mHasMenu && f.mMenuVisible) {
					show = true;
					f.onPrepareOptionsMenu(menu);
				}
			}
		}
		return show;
	}

	public boolean dispatchOptionsItemSelected(MenuItem item) {
		if (mActive != null) {
			for (int i = 0; i < mAdded.size(); i++) {
				Fragment f = mAdded.get(i);
				if (f != null && !f.mHidden && f.mHasMenu && f.mMenuVisible) {
					if (f.onOptionsItemSelected(item)) {
						return true;
					}
				}
			}
		}
		return false;
	}

	public boolean dispatchContextItemSelected(MenuItem item) {
		if (mActive != null) {
			for (int i = 0; i < mAdded.size(); i++) {
				Fragment f = mAdded.get(i);
				if (f != null && !f.mHidden) {
					if (f.onContextItemSelected(item)) {
						return true;
					}
				}
			}
		}
		return false;
	}

	public void dispatchOptionsMenuClosed(Menu menu) {
		if (mActive != null) {
			for (int i = 0; i < mAdded.size(); i++) {
				Fragment f = mAdded.get(i);
				if (f != null && !f.mHidden && f.mHasMenu && f.mMenuVisible) {
					f.onOptionsMenuClosed(menu);
				}
			}
		}
	}

	public static int reverseTransit(int transit) {
		int rev = 0;
		switch (transit) {
		case FragmentTransaction.TRANSIT_FRAGMENT_OPEN:
			rev = FragmentTransaction.TRANSIT_FRAGMENT_CLOSE;
			break;
		case FragmentTransaction.TRANSIT_FRAGMENT_CLOSE:
			rev = FragmentTransaction.TRANSIT_FRAGMENT_OPEN;
			break;
		case FragmentTransaction.TRANSIT_FRAGMENT_FADE:
			rev = FragmentTransaction.TRANSIT_FRAGMENT_FADE;
			break;
		}
		return rev;

	}

	public static final int ANIM_STYLE_OPEN_ENTER = 1;
	public static final int ANIM_STYLE_OPEN_EXIT = 2;
	public static final int ANIM_STYLE_CLOSE_ENTER = 3;
	public static final int ANIM_STYLE_CLOSE_EXIT = 4;
	public static final int ANIM_STYLE_FADE_ENTER = 5;
	public static final int ANIM_STYLE_FADE_EXIT = 6;

	public static int transitToStyleIndex(int transit, boolean enter) {
		int animAttr = -1;
		switch (transit) {
		case FragmentTransaction.TRANSIT_FRAGMENT_OPEN:
			animAttr = enter ? ANIM_STYLE_OPEN_ENTER : ANIM_STYLE_OPEN_EXIT;
			break;
		case FragmentTransaction.TRANSIT_FRAGMENT_CLOSE:
			animAttr = enter ? ANIM_STYLE_CLOSE_ENTER : ANIM_STYLE_CLOSE_EXIT;
			break;
		case FragmentTransaction.TRANSIT_FRAGMENT_FADE:
			animAttr = enter ? ANIM_STYLE_FADE_ENTER : ANIM_STYLE_FADE_EXIT;
			break;
		}
		return animAttr;
	}
}
